/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain an copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.beyondsw.palette;

import ohos.agp.colors.RgbColor;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.Paint;
import ohos.agp.render.Canvas;
import ohos.agp.render.BlendMode;
import ohos.agp.render.Path;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.render.Texture;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

import java.util.ArrayList;
import java.util.List;

/**
 * 简单的签名画板
 * Created by xwg
 *
 * @since 2021-05-27
 */
public class PaletteView extends Component implements Component.DrawTask, Component.TouchEventListener {
    private static final int MAX_CACHE_STEP = 20;
    private static final int PEN_ALPHA = 255;
    private static final int DEFAULT_DRAW_SIZE = 13; // 默认画笔的大小
    private static final int DEFAULT_ERASER_SIZE = 50; // 默认橡皮擦的画笔大小
    private static final int NUM2 = 2;
    private Paint mPaint;
    private Path mPath;
    private float mLastX;
    private float mLastY;
    private PixelMap mBufferBitmap;
    private Canvas mBufferCanvas;
    private List<DrawingInfo> mDrawingList;
    private List<DrawingInfo> mRemovedList;
    private int mDrawSize;
    private int mEraserSize;
    private int mPenAlpha = PEN_ALPHA;
    private boolean isCanEraser;
    private Callback mCallback;
    private Mode mMode = Mode.DRAW;

    /**
     * PaletteView
     *
     * @param context
     */
    public PaletteView(Context context) {
        super(context);
        init();
    }

    /**
     * PaletteView
     *
     * @param context
     * @param attrs
     */
    public PaletteView(Context context, AttrSet attrs) {
        super(context, attrs);
        init();
    }

    /**
     * PaletteView
     *
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    public PaletteView(Context context, AttrSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE_STYLE);
        mPaint.setFilterBitmap(true);
        mPaint.setStrokeJoin(Paint.Join.ROUND_JOIN);
        mPaint.setStrokeCap(Paint.StrokeCap.ROUND_CAP);
        mDrawSize = DEFAULT_DRAW_SIZE;
        mEraserSize = DEFAULT_ERASER_SIZE;
        mPaint.setStrokeWidth(mDrawSize);
        mPaint.setColor(Color.BLACK);

        // 添加draw task
        addDrawTask(this);

        // 设置TouchEvent响应事件
        setTouchEventListener(this);
    }

    private void initBuffer() {
        mBufferBitmap = createBitmap(getWidth(), getHeight());
        Texture texture = new Texture(mBufferBitmap);
        mBufferCanvas = new Canvas(texture);
    }

    private static PixelMap createBitmap(int width, int height) {
        PixelMap.InitializationOptions opts = new PixelMap.InitializationOptions();
        opts.size = new Size(width, height);
        opts.pixelFormat = PixelFormat.ARGB_8888;
        opts.editable = true;
        return PixelMap.create(opts);
    }

    /**
     * DrawingInfo
     *
     * @since 2021-05-28
     */
    private abstract static class DrawingInfo {
        Paint paint;

        abstract void draw(Canvas canvas);
    }

    /**
     * DrawingInfo
     *
     * @since 2021-05-28
     */
    private static class PathDrawingInfo extends DrawingInfo {
        Path path;

        @Override
        void draw(Canvas canvas) {
            canvas.drawPath(path, paint);
        }
    }

    public Mode getDrawMode() {
        return mMode;
    }

    /**
     * 设置画笔的类型
     *
     * @param mode
     */
    public void setDrawMode(Mode mode) {
        if (mode != mMode) {
            mMode = mode;
            if (mMode == Mode.DRAW) { // 画笔
                mPaint.setBlendMode(null);
                mPaint.setStrokeWidth(mDrawSize);
            } else { // 橡皮擦
                mPaint.setBlendMode(BlendMode.DST_OUT);
                mPaint.setStrokeWidth(mEraserSize);
            }
        }
    }

    /**
     * 设置橡皮擦的宽度
     *
     * @param size
     */
    public void setEraserSize(int size) {
        mEraserSize = size;
    }

    /**
     * 设置画笔的宽度
     *
     * @param size
     */
    public void setPenRawSize(int size) {
        mDrawSize = size;
        if (mMode == Mode.DRAW) {
            mPaint.setStrokeWidth(mDrawSize);
        }
    }

    /**
     * 设置画笔的颜色
     *
     * @param color
     */
    public void setPenColor(Color color) {
        mPaint.setColor(color);
    }

    /**
     * 重新绘制
     */
    private void reDraw() {
        if (mDrawingList != null && mBufferCanvas != null) {
            if (mBufferBitmap != null) {
                mBufferBitmap.release();
            }
            mBufferBitmap = createBitmap(getWidth(), getHeight());
            Texture texture = new Texture(mBufferBitmap);
            mBufferCanvas.setTexture(texture);
            for (DrawingInfo drawingInfo : mDrawingList) {
                drawingInfo.draw(mBufferCanvas);
            }
            invalidate();
        }
    }

    public Color getPenColor() {
        return mPaint.getColor();
    }

    public int getPenSize() {
        return mDrawSize;
    }

    public int getEraserSize() {
        return mEraserSize;
    }

    /**
     * 设置画笔的透明度
     *
     * @param alpha
     */
    public void setPenAlpha(int alpha) {
        mPenAlpha = alpha;
        if (mMode == Mode.DRAW) {
            mPaint.setAlpha(alpha);
        }
    }

    /**
     * 获取画笔的透明度
     *
     * @return alpha
     */
    public int getPenAlpha() {
        return mPenAlpha;
    }

    /**
     * 是否可以撤销
     *
     * @return 是否可以撤销
     */
    public boolean canRedo() {
        return mRemovedList != null && mRemovedList.size() > 0;
    }

    /**
     * 是否可以恢复
     *
     * @return 是否可以恢复
     */
    public boolean canUndo() {
        return mDrawingList != null && mDrawingList.size() > 0;
    }

    /**
     * 恢复上次状态
     */
    public void redo() {
        int size = mRemovedList == null ? 0 : mRemovedList.size();
        if (size > 0) {
            DrawingInfo info = mRemovedList.remove(size - 1);
            mDrawingList.add(info);
            isCanEraser = true;
            reDraw();
            if (mCallback != null) {
                mCallback.onUndoRedoStatusChanged();
            }
        }
    }

    /**
     * 撤销
     */
    public void undo() {
        int size = mDrawingList == null ? 0 : mDrawingList.size();
        if (size > 0) {
            DrawingInfo info = mDrawingList.remove(size - 1);
            if (mRemovedList == null) {
                mRemovedList = new ArrayList<>(MAX_CACHE_STEP);
            }
            if (size == 1) {
                isCanEraser = false;
            }
            mRemovedList.add(info);
            reDraw();
            if (mCallback != null) {
                mCallback.onUndoRedoStatusChanged();
            }
        }
    }

    /**
     * 清空画布
     */
    public void clear() {
        if (mBufferBitmap != null) {
            if (mDrawingList != null) {
                mDrawingList.clear();
            }
            if (mRemovedList != null) {
                mRemovedList.clear();
            }
            isCanEraser = false;
            mBufferBitmap.release();
            mBufferBitmap = createBitmap(getWidth(), getHeight());
            mBufferCanvas.setTexture(new Texture(mBufferBitmap));
            invalidate();
            if (mCallback != null) {
                mCallback.onUndoRedoStatusChanged();
            }
        }
    }

    /**
     * 将组件内容转化为 PixelMap
     *
     * @return PixelMap
     */
    public PixelMap buildPixelMap() {
        // 创建PixelMap的bitmapAll对象
        PixelMap bitmapAll = createBitmap(getWidth(), getHeight());
        Texture texture = new Texture(bitmapAll);
        Canvas canvas = new Canvas();
        canvas.setTexture(texture);

        // 画背景
        canvas.save();
        Element bgEl = getBackgroundElement();
        if (bgEl instanceof PixelMapElement) {
            RectFloat rectFloat = new RectFloat(0, 0, getWidth(), getBottom());
            canvas.drawPixelMapHolderRect(new PixelMapHolder(((PixelMapElement) bgEl).getPixelMap()),
                    rectFloat, new Paint());
        } else if (bgEl instanceof ShapeElement) {
            RgbColor[] colors = ((ShapeElement) bgEl).getRgbColors();
            if (colors != null && colors.length > 0) {
                Paint paint = new Paint();
                Color color = new Color(Color.argb(colors[0].getAlpha(),
                        colors[0].getRed(), colors[0].getGreen(), colors[0].getBlue()));
                paint.setColor(color);
                canvas.drawRect(((ShapeElement) bgEl).getBounds(), paint);
            }
        }
        canvas.restore();
        canvas.drawPixelMapHolder(new PixelMapHolder(bitmapAll), 0, 0, new Paint());

        // 画当前画板的内容
        canvas.drawPixelMapHolder(new PixelMapHolder(mBufferBitmap), 0, 0, new Paint());

        return bitmapAll;
    }

    /**
     * 保存绘制path
     */
    private void saveDrawingPath() {
        if (mDrawingList == null) {
            mDrawingList = new ArrayList<>(MAX_CACHE_STEP);
        } else if (mDrawingList.size() == MAX_CACHE_STEP) {
            mDrawingList.remove(0);
        }
        Path cachePath = new Path(mPath);
        Paint cachePaint = new Paint(mPaint);
        PathDrawingInfo info = new PathDrawingInfo();
        info.path = cachePath;
        info.paint = cachePaint;
        mDrawingList.add(info);
        isCanEraser = true;
        if (mCallback != null) {
            mCallback.onUndoRedoStatusChanged();
        }
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        if (mBufferBitmap != null) {
            PixelMapHolder pixelMapHolder = new PixelMapHolder(mBufferBitmap);
            canvas.drawPixelMapHolder(pixelMapHolder, 0, 0, new Paint());
        }
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent event) {
        if (!isEnabled()) {
            return false;
        }
        final int action = event.getAction();
        MmiPoint point = event.getPointerPosition(event.getIndex());
        final float pointX = point.getX();
        final float pointY = point.getY();
        switch (action) {
            case TouchEvent.PRIMARY_POINT_DOWN:
            case TouchEvent.OTHER_POINT_DOWN:
                mLastX = pointX;
                mLastY = pointY;
                if (mPath == null) {
                    mPath = new Path();
                }
                mPath.moveTo(pointX, pointY);
                break;
            case TouchEvent.POINT_MOVE:

                // 这里终点设为两点的中心点的目的在于使绘制的曲线更平滑，如果终点直接设置为x,y，效果和lineto是一样的,实际是折线效果
                mPath.quadTo(mLastX, mLastY, (pointX + mLastX) / NUM2, (pointY + mLastY) / NUM2);
                if (mBufferBitmap == null) {
                    initBuffer();
                }
                if (mMode == Mode.ERASER && !isCanEraser) {
                    break;
                }
                mBufferCanvas.drawPath(mPath, mPaint);
                invalidate();
                mLastX = pointX;
                mLastY = pointY;
                break;
            case TouchEvent.OTHER_POINT_UP:
            case TouchEvent.PRIMARY_POINT_UP:
                if (mMode == Mode.DRAW || isCanEraser) {
                    saveDrawingPath();
                }
                mPath.reset();
                break;
            default:
                break;
        }
        return true;
    }

    /**
     * 画笔类型
     *
     * @since 2021-05-27
     */
    public enum Mode {
        /**
         * 画笔
         */
        DRAW,

        /**
         * 橡皮
         */
        ERASER
    }

    /**
     * 设置撤销、恢复的状态监听
     *
     * @param callback
     */
    public void setCallback(Callback callback) {
        mCallback = callback;
    }

    /**
     * 撤销、恢复的状态监听
     *
     * @since 2021-05-27
     */
    public interface Callback {
        /**
         * 撤销、恢复的状态监听
         */
        void onUndoRedoStatusChanged();
    }
}
